from datetime import datetime
from decimal import Decimal
from typing import List, Optional

from django.db import transaction
from django_redis import get_redis_connection
from ninja import schema, ModelSchema, Field
from ninja_extra import service_resolver
from ninja_extra.controllers import RouteContext
from ninja_extra.exceptions import APIException

from carts.schemas import CartSKUSchema
from goods.models import SKU
from .models import OrderInfo, OrderGoods


class OrderSchemaOut(schema.Schema):
    freight: Decimal
    skus: List[CartSKUSchema]


class OrderCommitSchema(ModelSchema):
    order_id: Optional[str] = None
    address_id: int = Field(..., alias="address")

    class Meta:
        model = OrderInfo
        fields = ['pay_method', 'order_id']

    def create(self):
        context: RouteContext = service_resolver(RouteContext)
        _data = self.dict(exclude_none=True)
        # 生成订单编号
        user = context.request.user
        order_id = datetime.now().strftime('%Y%m%d%H%M%S') + f'{user.id}'
        # 获取传回的收货地址
        address = _data['address_id']
        # 支付方式
        pay_method = _data['pay_method']
        # 订单状态
        status = (OrderInfo.ORDER_STATUS_ENUM['UNPAID']
                  if pay_method == OrderInfo.PAY_METHODS_ENUM['ALIPAY']
                  else OrderInfo.ORDER_STATUS_ENUM['UNSEND'])
        # 手动开启数据库事务
        with transaction.atomic():
            # 创建事务保存点
            save_point = transaction.savepoint()
            try:
                # 生成订单
                order_obj = OrderInfo.objects.create(
                    order_id=order_id,
                    user=user,
                    total_count=0,
                    total_amount=Decimal('0.00'),
                    freight=Decimal('10.00'),
                    status=status,
                    **_data,
                    # address_id=_data['address_id'],
                    # pay_method=_data['pay_method'],
                )
                # 从redis中取出购物车中待结算商品
                redis_conn = get_redis_connection('cart')
                cart_dict_redis = redis_conn.hgetall(f'cart_{user.id}')
                select_ids = redis_conn.smembers(f'selected_{user.id}')

                # 保证数据准确性，通过for循环来操作，有库存不足时抛出异常
                for sku_id_bytes in select_ids:
                    while True:  # 慎用：只要有库存允许可以一直下单
                        # 商品对象
                        sku = SKU.objects.filter(id=sku_id_bytes).first()
                        # 购买数量
                        buy_count = int(cart_dict_redis[sku_id_bytes])
                        # 当前商品的销量和库存数
                        origin_sales = sku.sales
                        origin_stock = sku.stock

                        # 检查库存
                        if buy_count > origin_stock:
                            raise APIException('库存不足')
                        # 计算新的库存和销量
                        new_sales = origin_sales + buy_count
                        new_stock = origin_stock - buy_count
                        # sku.sales = new_sales
                        # sku.stock = new_stock
                        # sku.save()
                        # 使用乐观锁的方式修改数据

                        # 使用update更新数据时，会返回更新数据的条数
                        res = SKU.objects.filter(stock=origin_stock, id=sku_id_bytes).update(stock=new_stock,
                                                                                             sales=new_sales)
                        if res == 0:
                            continue

                        # 修改SPU销量
                        spu = sku.goods
                        spu.sales = spu.sales + buy_count
                        spu.save()
                        # 保存订单商品信息
                        OrderGoods.objects.create(
                            order=order_obj,
                            sku=sku,
                            count=buy_count,
                            price=sku.price,
                        )

                        # 计算总数量和总价
                        order_obj.total_count += buy_count
                        order_obj.total_amount += (sku.price * buy_count)
                        break  # 下单成功后跳出循环
                # 总价=商品总价再加上运费
                order_obj.total_amount += order_obj.freight
                order_obj.save()
            except Exception:
                # 有异常直接回滚
                transaction.savepoint_rollback(save_point)
                raise APIException('库存不足')
            else:
                # 没有异常则提交事务
                transaction.savepoint_commit(save_point)
        # 清除购物车数据
        pl = redis_conn.pipeline()
        pl.hdel(f'cart_{user.id}', *select_ids)
        pl.srem(f'selected_{user.id}', *select_ids)
        pl.execute()
        return order_obj


class OrderCommitSchemaOut(ModelSchema):
    class Meta:
        model = OrderInfo
        fields = ['order_id', 'total_amount', 'pay_method']
